package com.translate.web.service.upload.impl;

import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.net.URL;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.aliyun.oss.HttpMethod;
import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClient;
import com.aliyun.oss.OSSClientBuilder;
import com.aliyun.oss.common.auth.CredentialsProviderFactory;
import com.aliyun.oss.common.auth.DefaultCredentialProvider;
import com.aliyun.oss.internal.OSSHeaders;
import com.aliyun.oss.model.CompleteMultipartUploadRequest;
import com.aliyun.oss.model.CompleteMultipartUploadResult;
import com.aliyun.oss.model.GeneratePresignedUrlRequest;
import com.aliyun.oss.model.InitiateMultipartUploadRequest;
import com.aliyun.oss.model.InitiateMultipartUploadResult;
import com.aliyun.oss.model.PartETag;
import com.aliyun.oss.model.UploadPartRequest;
import com.aliyun.oss.model.UploadPartResult;
import com.aliyuncs.DefaultAcsClient;
import com.aliyuncs.auth.sts.AssumeRoleRequest;
import com.aliyuncs.auth.sts.AssumeRoleResponse;
import com.aliyuncs.exceptions.ClientException;
import com.aliyuncs.http.MethodType;
import com.aliyuncs.profile.DefaultProfile;
import com.aliyuncs.profile.IClientProfile;
import com.common.util.EncryptUtil;
import com.common.util.FileFormatVerifyUtil;
import com.common.util.RandomString;
import com.common.util.WithdrawalOfSurplus;
import com.msg.ResponseCode;
import com.returns.value.CodeAndData;
import com.returns.value.ICodeAndData;
import com.translate.web.config.contract.IOSSConfig;
import com.translate.web.service.upload.IUploadService;

@Service()
public class OssServiceImpl implements IUploadService {

	private static Logger LOGGER = LoggerFactory.getLogger(OssServiceImpl.class);

	private IOSSConfig ossConfig;

	private static Map<String, String> IMAGE_TYPE = new HashMap<>();

	static {
		IMAGE_TYPE.put(".jpg", ".jpg");
		IMAGE_TYPE.put(".png", ".png");
		IMAGE_TYPE.put(".jpeg", ".jpeg");

		IMAGE_TYPE.put("image/png", "image/png");

		IMAGE_TYPE.put("image/png", "image/jpeg");
	}

	@Autowired
	public void setOssConfig(IOSSConfig ossConfig) {
		this.ossConfig = ossConfig;
	}

	private OSSClient getOssClient() {
		return new OSSClient(ossConfig.getEndpoint(), ossConfig.getAccessKeyId(), ossConfig.getAccessKeySecret());
	}

	/**
	 * 
	 * @param fileHeader 文件头
	 */
	public <T> ICodeAndData<T> stsUpload(byte[] fileHeader, String fileType) {

//		boolean b = FileFormatVerifyUtil.suffixVerify(fileHeader, fileType);
//	
//		if (!b) {
//			return new CodeAndData<T>(ResponseCode.ERROR_FILE_TYPE, null);
//		}

		AssumeRoleResponse temporaryAccessCredentials = this.temporaryAccessCredentials();

		return new CodeAndData<T>(ResponseCode.SUCCESS, (T) temporaryAccessCredentials);
	}

	public <V> ICodeAndData<Map<String, Object>> signURL(String fileName, /* String type, */ Function<V, String> path) {

		// 以华东1（杭州）的外网Endpoint为例，其它Region请按实际情况填写。
		// 从环境变量中获取访问凭证。运行本代码示例之前，请确保已设置环境变量OSS_ACCESS_KEY_ID和OSS_ACCESS_KEY_SECRET。
		DefaultCredentialProvider credentialsProvider = CredentialsProviderFactory
				.newDefaultCredentialProvider(ossConfig.getAccessKeyId(), ossConfig.getAccessKeySecret());
		// 填写Bucket名称，例如examplebucket。
		String bucketName = this.ossConfig.getBucketName();
		// 填写Object完整路径，例如exampleobject.txt。Object完整路径中不能包含Bucket名称。
		String objectName = getDir(path, fileName, fileName.substring(fileName.indexOf('.')));
		// 填写本地文件的完整路径。如果未指定本地路径，则默认从示例程序所属项目对应本地路径中上传文件。

		// 创建OSSClient实例
		OSS ossClient = new OSSClientBuilder().build(ossConfig.getEndpoint(), credentialsProvider);

		// 设置请求头。
		Map<String, String> headers = new HashMap<String, String>();
		/*
		 * // 指定Object的存储类型。 headers.put(OSSHeaders.STORAGE_CLASS,
		 * StorageClass.Standard.toString()); // 指定ContentType。 ;
		 */

//       String typeValue = IMAGE_TYPE.get(type);
//       
//       if (null == typeValue) {
//       	return  new CodeAndData<Map<String, String>>(ResponseCode.ERROR_FILE_TYPE, null);
//       }
		String value = "multipart/form-data";
		headers.put(OSSHeaders.CONTENT_TYPE, value);
		
//		String encryptSHA = EncryptUtil.encryptSHA(objectName);
//		headers.put(OSSHeaders.CONTENT_MD5, encryptSHA);

		// 设置用户自定义元信息。
//		Map<String, String> userMetadata = new HashMap<String, String>();
		/*
		 * userMetadata.put("key1","value1"); userMetadata.put("key2","value2");
		 */

		URL signedUrl = null;
		try {
			// 指定生成的签名URL过期时间，单位为毫秒。本示例以设置过期时间为1小时为例。
			Date expiration = new Date(new Date().getTime() + 3600 * 1000L);

			// 生成签名URL。
			GeneratePresignedUrlRequest request = new GeneratePresignedUrlRequest(bucketName, objectName,
					HttpMethod.PUT);
			// 设置过期时间。
			request.setExpiration(expiration);

			// 将请求头加入到request中。
			request.setHeaders(headers);
			// 添加用户自定义元信息。
//                 request.setUserMetadata(userMetadata);

			// 通过HTTP PUT请求生成签名URL。
			signedUrl = ossClient.generatePresignedUrl(request);
			// 打印签名URL。
			System.out.println("signed url for putObject: " + signedUrl);

			Map<String, Object> map = new HashMap<String, Object>();

			map.put("url", signedUrl.toString().replace("http","https"));

			map.put("expiration", expiration);
			
//			map.put(OSSHeaders.CONTENT_MD5, encryptSHA);

			map.put(OSSHeaders.CONTENT_TYPE, value);

			map.put("objectName", objectName);
			
			return new CodeAndData<Map<String, Object>>(ResponseCode.SUCCESS, map);

		} catch (Exception oe) {
			String string = "Caught an OSSException, which means your request made it to OSS,but was rejected with an error response for some reason.";
			System.out.println(string);

			return new CodeAndData<Map<String, Object>>(string, ResponseCode.ERROR.getCode());
		}
	}

	private <V> String getDir(Function<V, String> function, String originalFileName, String suffix) {
		SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");

		Date date = new Date();

		String perfix = function.apply((V) ossConfig);

		String dir = perfix + sdf.format(date);

		RandomString randomString = new RandomString(5);

		String fileName = originalFileName.substring(0, originalFileName.lastIndexOf("."));

		originalFileName = fileName + randomString.getString() + suffix;

		return dir + "/" + originalFileName;
	}

	/**
	 * sts 授权签名
	 */
	protected AssumeRoleResponse temporaryAccessCredentials() {

		// STS接入地址，例如sts.cn-hangzhou.aliyuncs.com。
		String endpoint = "https://" + ossConfig.getBucketName() + ossConfig.getEndpoint();
		// 从环境变量中获取步骤1生成的RAM用户的访问密钥（AccessKey ID和AccessKey Secret）。
		String accessKeyId = ossConfig.getAccessKeyId();
		String accessKeySecret = ossConfig.getAccessKeySecret();
		// 从环境变量中获取步骤3生成的RAM角色的RamRoleArn。
		String roleArn = ossConfig.getArn();
		// 自定义角色会话名称，用来区分不同的令牌，例如可填写为SessionTest。
		String roleSessionName = "keteng-ps";
		// 以下Policy用于限制仅允许使用临时访问凭证向目标存储空间examplebucket下的src目录上传文件。
		// 临时访问凭证最后获得的权限是步骤4设置的角色权限和该Policy设置权限的交集，即仅允许将文件上传至目标存储空间examplebucket下的src目录。
		// 如果policy为空，则用户将获得该角色下所有权限。
		String policy = "{\r\n" + "    \"Version\": \"1\",\r\n" + "    \"Statement\": [\r\n" + "        {\r\n"
				+ "            \"Effect\": \"Allow\",\r\n" + "            \"Action\": [\r\n"
				+ "                \"oss:PutObject\",\r\n" + "                \"oss:GetObject\"\r\n"
				+ "            ],\r\n" + "            \"Resource\": \"*\"\r\n" + "        }\r\n" + "    ]\r\n" + "}";
		// 设置临时访问凭证的有效时间为3600秒。
		Long durationSeconds = ossConfig.getDurationSeconds();
		try {
			// regionId表示RAM的地域ID。以华东1（杭州）地域为例，regionID填写为cn-hangzhou。也可以保留默认值，默认值为空字符串（""）。
			String regionId = ossConfig.getArea();
			// 添加endpoint。适用于Java SDK 3.12.0及以上版本。
//            DefaultProfile.addEndpoint(regionId, "Sts", endpoint);
			// 添加endpoint。适用于Java SDK 3.12.0以下版本。
			DefaultProfile.addEndpoint("", regionId, "Sts", endpoint);
			// 构造default profile。
			IClientProfile profile = DefaultProfile.getProfile(regionId, accessKeyId, accessKeySecret);
			// 构造client。
			DefaultAcsClient client = new DefaultAcsClient(profile);
			final AssumeRoleRequest request = new AssumeRoleRequest();
			// 适用于Java SDK 3.12.0及以上版本。
//            request.setSysMethod(MethodType.POST);
			// 适用于Java SDK 3.12.0以下版本。
			request.setMethod(MethodType.POST);
			request.setRoleArn(roleArn);
			request.setRoleSessionName(roleSessionName);
			request.setPolicy(policy);
			request.setDurationSeconds(durationSeconds);
			final AssumeRoleResponse response = client.getAcsResponse(request);
			System.out.println("Expiration: " + response.getCredentials().getExpiration());
			System.out.println("Access Key Id: " + response.getCredentials().getAccessKeyId());
			System.out.println("Access Key Secret: " + response.getCredentials().getAccessKeySecret());
			System.out.println("Security Token: " + response.getCredentials().getSecurityToken());
			System.out.println("RequestId: " + response.getRequestId());
			return response;
		} catch (ClientException e) {
			System.out.println("Failed：");
			System.out.println("Error code: " + e.getErrCode());
			System.out.println("Error message: " + e.getErrMsg());
			System.out.println("RequestId: " + e.getRequestId());
		}
		return null;

	}

	public <V> ICodeAndData<String> uploadFileToOSS(InputStream inputStreamSource, String fileName,
			Function<V, String> function, long totalSize) {

		InputStream inputStream = null;

		OSSClient ossClient = getOssClient();

		try {
			// 创建OSS实例。
			// 获取上传文件的输入流
			inputStream = (InputStream) inputStreamSource;

			byte[] b = new byte[16];

			inputStream.read(b);

			boolean pass = FileFormatVerifyUtil.suffixVerify(b, ".glbf");
//			
			if (false == pass) {
				return new CodeAndData<String>(ResponseCode.ERROR_FILE_TYPE, null);
			}

			String bucketName = this.ossConfig.getBucketName();

			// 获取文件名称

			SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");

			Date date = new Date();

			String perfix = function.apply((V) ossConfig);

			String dir = perfix + sdf.format(date);

			RandomString randomString = new RandomString(5);

			String suffix = fileName.substring(fileName.indexOf('.'));

			fileName = (new SimpleDateFormat("yyyyMMddHHmmss")).format(date) + "-" + randomString.getString() + suffix;

			dir = dir + "/" + fileName;

			long partSize = this.ossConfig.getSize();

			// 创建InitiateMultipartUploadRequest对象。
			InitiateMultipartUploadRequest request = new InitiateMultipartUploadRequest(bucketName, dir);

			// 初始化分片。
			InitiateMultipartUploadResult upresult = ossClient.initiateMultipartUpload(request);

			String uploadId = upresult.getUploadId();

			// 总共分多少个
			long count = WithdrawalOfSurplus.ofSurplus(totalSize, partSize);

			long startPos = 0, curPartSize = 0;

			UploadPartRequest uploadPartRequest;

			// partETags是PartETag的集合。PartETag由分片的ETag和分片号组成。
			List<PartETag> partETags = new ArrayList<PartETag>();

//			FileDescriptor fileDescriptor = ((FileInputStream)inputStream).getFD();

			String path = "";

			Field pathField = inputStream.getClass().getDeclaredField("path");

			pathField.setAccessible(true);

			path = (String) pathField.get(inputStream);

			File file = new File(path);

			inputStream.close();

			InputStream fileInputStream;

			for (int i = 0; i < count; i++) {

				startPos = i * partSize;

				fileInputStream = new FileInputStream(file);

				curPartSize = (i + 1 == count) ? (totalSize - startPos) : partSize;

				// 跳过已经上传的分片。
				fileInputStream.skip(startPos);
				uploadPartRequest = new UploadPartRequest();
				uploadPartRequest.setBucketName(bucketName);
				uploadPartRequest.setKey(dir);
				uploadPartRequest.setUploadId(uploadId);
				uploadPartRequest.setInputStream(fileInputStream);
				// 设置分片大小。除了最后一个分片没有大小限制，其他的分片最小为100 KB。
				uploadPartRequest.setPartSize(curPartSize);
				// 设置分片号。每一个上传的分片都有一个分片号，取值范围是1~10000，如果超出此范围，OSS将返回InvalidArgument错误码。
				uploadPartRequest.setPartNumber(i + 1);
				// 每个分片不需要按顺序上传，甚至可以在不同客户端上传，OSS会按照分片号排序组成完整的文件。
				UploadPartResult uploadPartResult = ossClient.uploadPart(uploadPartRequest);
//				// 每次上传分片之后，OSS的返回结果包含PartETag。PartETag将被保存在partETags中。
				partETags.add(uploadPartResult.getPartETag());

			}
			// 创建CompleteMultipartUploadRequest对象。
			// 在执行完成分片上传操作时，需要提供所有有效的partETags。OSS收到提交的partETags后，会逐一验证每个分片的有效性。当所有的数据分片验证通过后，OSS将把这些分片组合成一个完整的文件。
			CompleteMultipartUploadRequest completeMultipartUploadRequest = new CompleteMultipartUploadRequest(
					bucketName, dir, uploadId, partETags);

			// 如果需要在完成分片上传的同时设置文件访问权限，请参考以下示例代码。
			// completeMultipartUploadRequest.setObjectACL(CannedAccessControlList.Private);
			// 指定是否列举当前UploadId已上传的所有Part。如果通过服务端List分片数据来合并完整文件时，以上CompleteMultipartUploadRequest中的partETags可为null。
			// Map<String, String> headers = new HashMap<String, String>();
			// 如果指定了x-oss-complete-all:yes，则OSS会列举当前UploadId已上传的所有Part，然后按照PartNumber的序号排序并执行CompleteMultipartUpload操作。
			// 如果指定了x-oss-complete-all:yes，则不允许继续指定body，否则报错。
			// headers.put("x-oss-complete-all","yes");
			// completeMultipartUploadRequest.setHeaders(headers);

			// 完成分片上传。
			CompleteMultipartUploadResult completeMultipartUploadResult = ossClient
					.completeMultipartUpload(completeMultipartUploadRequest);
			System.out.println(completeMultipartUploadResult.getETag());

			// 调用oss实例中的方法实现上传
			// 参数1： Bucket名称
			// 参数2： 上传到oss文件路径和文件名称 /aa/bb/1.jpg
			// 参数3： 上传文件的输入流
//            ossClient.putObject(ALIYUN_OSS_BUCKET_NAME, dir, inputStream);

			// 把上传后文件路径返回
			// 需要把上传到阿里云oss路径手动拼接出来
			// https://achang-edu.oss-cn-hangzhou.aliyuncs.com/default.gif
			String url = "https://" + ossConfig.getBucketName() + "." + ossConfig.getEndpoint() + "/" + dir;

			inputStream.close();

//			fileInputStream.close();
			return new CodeAndData<String>(ResponseCode.SUCCESS, url);
		} catch (Exception e) {

			e.printStackTrace();
			return new CodeAndData<String>(ResponseCode.ERROR, e.getMessage());
		} finally {
			// 关闭OSSClient。
			ossClient.shutdown();
		}
	}

}
